/*
 *  Copyright (C) 2011 glauco
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.balisunrise.swing.commons;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import javax.swing.SwingUtilities;
import org.balisunrise.application.Application;
import org.balisunrise.builder.FactoryException;
import org.balisunrise.common.Bindable;
import org.balisunrise.common.Binding;
import org.balisunrise.common.BindingGroup;
import org.balisunrise.common.Criterion;
import org.balisunrise.common.Criterionable;
import org.balisunrise.common.EntityFieldType;
import org.balisunrise.common.EntityHolder;
import org.balisunrise.common.Event;
import org.balisunrise.common.FieldLayout;
import org.balisunrise.common.FieldLayoutGroup;
import org.balisunrise.common.NotFoundException;
import org.balisunrise.common.NotModifiedException;
import org.balisunrise.common.Query;
import org.balisunrise.common.SelectionAction;
import org.balisunrise.common.abstracts.AbstractSelectionFieldLayout;
import org.balisunrise.common.defaults.DefaultEntityHolder;
import org.balisunrise.swing.components.SelectionMessenger;

/**
 * Esta interface representa um campo de um entidade, porém este campo não é uma
 * coluna da tabela da entidade, e sim uma outra entidade com sua própria tabela
 * seus próprios campos.
 * Objetos dessa interface possuem os LayoutableField's da classe que o campo
 * representa, portanto esses objetos são LayoutableField e também FieldsGroup.
 * Esse objeto não possui um FieldLayout próprio, e sim um conjunto de FieldLayout
 * dos LayoutableField que ele possui.
 * 
 * @author glauco
 */
public class SelectionPanelField extends AbstractSelectionFieldLayout
        implements org.balisunrise.common.SelectionPanelField,
        Runnable{

    public static final String PROP_ENTITY_HOLDERS = "entityHolders";
    public static final String PROP_CRITERION = "criterion";
    public static final String PROP_FIELD_PRE_NAME = "fieldPreName";

    /**
     * Mapa de repositório de entidades.
     */
    private HashMap<String,EntityHolder> entityHolders;

    /**
     * criterion deste SelectionPanelField.
     */
    private Criterion criterion;

    /**
     * Nome predecessor do nome do campo para filtrar.
     */
    private String fieldPreName;

    /**
     * Último FieldLayout que é Criterionable.
     */
    private FieldLayout lastCriterionable;

    /**
     * Construtor
     */
    public SelectionPanelField() {
        entityHolders = new HashMap<String, EntityHolder>();
    }


    /**
     * get fieldPreName.
     */
    public String getFieldPreName() {
        return fieldPreName;
    }

    /**
     * set fieldPreName.
     */
    public void setFieldPreName(String fieldPreName) {
        String old = this.fieldPreName;
        this.fieldPreName = fieldPreName;
        changeSupport.firePropertyChange(PROP_FIELD_PRE_NAME, old, fieldPreName);
    }

    /**
     * Set's the selectedEntity.<br>
     * Para cada entityHolders, se existir, é atualiza seus valores.
     *
     * @param selectedEntity the selectedEntity
     */
    @Override
    public void setEntity(Serializable entity) {

        System.out.println(entity);

        super.setEntity(entity);

        // atualiza entityContainers
        for (String s : entityHolders.keySet()) {
            entityHolders.get(s).updateFromSource(entity, s);
        }

        for (FieldLayout fl : layouts) {
            if(fl instanceof CriterionablePanelField){
                CriterionablePanelField cpf = (CriterionablePanelField) fl;
                cpf.setCriterionValue(cpf.getValue());
            }
        }
    }

    /**
     * Atribui a Ação de seleção de entidades.<br>
     * É adicionado a R.V. da ação ao último SelectionPanelField deste grupo.
     *
     * @param selectionAction the SelectionAction.
     */
    @Override
    public void setSelectionAction(SelectionAction selectionAction) {
        
        super.setSelectionAction(selectionAction);

        // atribui o grupo desta ação de seleção de entidades.
        selectionAction.setSelectionFieldLayout(this);
        
        if(lastCriterionable != null)
            lastCriterionable.addFieldComponent(selectionAction);
    }

    /**
     * Limpa os dados dos componentes de Representação Visual.
     */
    @Override
    public void clearComponentsValues() {
        for (org.balisunrise.common.FieldLayout fl : layouts) {
            fl.clearComponentsValues();
        }
    }

    /**
     *
     * Retorna o tipo de EntityField.
     * @return EntityFieldType.SELECTION_PANEL_FIELD
     */
    @Override
    public EntityFieldType getEntityFieldType() {
        return EntityFieldType.SELECTION_PANEL_FIELD;
    }

    /**
     * Cria um Binding e o adiciona ao BindingGroup.
     * Faz o vinculo entre a propriedade do source e a propriedade
     * selectedEntity deste grupo.<br>
     * Depois, para cada PanelField deste grupo é feito o vinculo deste
     * objeto e a propriedade selectedEntity.<br>
     * No caso de algum PanelField deste grupo ser um outro SelectionPanelField,
     * é criado um EntityHolder e feito o vinculo entre o SelectionPanelField e
     * o EntityHolder.
     *
     * @param bindingGroup grupo de Bindings
     * @param source entidade com propriedades (campos) para vincular.
     * @param property propriedade do source a ser vinculada.
     */
    @Override
    public void bindComponent(BindingGroup bg, Object source, String property) {
        // Propriedade do source para bind deste objeto.
        String thisProperty = property;
        if (!(source instanceof EntityHolder))
            thisProperty += "." + fieldName;

        Binding binding = (Binding) Application.get().getBindingBuilder()
                .createBinding(bg, source, thisProperty, this, PROP_ENTITY);

        // TODO: verificar este BindingListener
        //binding.addListener(new BindingListener(this));

        for (FieldLayout fl : layouts) {

            // se for uma seleção
            if(fl instanceof org.balisunrise.common.SelectionPanelField){
                org.balisunrise.common.SelectionPanelField spf =
                        (org.balisunrise.common.SelectionPanelField) fl;
                // cria holder
                EntityHolder eh = new DefaultEntityHolder();
                // bind no holder
                spf.bindComponent(bg, eh, EntityHolder.PROP_ENTITY);
                // adiciona o holder ao mapa.
                entityHolders.put(fl.getFieldName(), eh);
            } else if(fl instanceof Bindable){
                Bindable b = (Bindable) fl;
                b.bindComponent(bg,this,PROP_ENTITY);
            }
        }
    }

    /**
     * Retorna uma Query.
     */
    @Override
    public Query getQuery() {
        try{
            Query query = Application.get()
                    .getFactory()
                    .createBaliQuery(fieldClass);
            List<Criterionable> criterionables = extractCriterionables(this);
            for (Criterionable c : criterionables) {
                query.addCriterion(c.getCriterion());
            }
            return query;
        } catch (FactoryException fex){
            Application.get().getLogger().log(Level.SEVERE,
                    "SelectionPanelField não pode criar Query", fex);
            return null;
        }
    }

    /**
     * Verifica se os valores do filtro foram modificados desde a última
     * chamada getCriterion().
     */
    @Override
    public boolean isModified() {
        List<Criterionable> criterionables = extractCriterionables(this);
        for (Criterionable c : criterionables) {
            if(c.isModified())
                return true;
        }
        return false;
    }

    /**
     * Retorna um Criterion.
     * @return Retorna um Criterion.
     */
    @Override
    public Criterion getCriterion() {
        if (criterion == null)
            try{
                criterion = Application.get()
                        .getFactory()
                        .createBaliCriterion(fieldName);
                if(fieldPreName != null && fieldPreName.length() > 0){
                    if(fieldPreName.contains("."))
                        for (String s : fieldPreName.split(".")) {
                            criterion.addPreName(s);
                        }
                    else
                        criterion.addPreName(fieldPreName);
                }
            }catch (FactoryException fex){
                Application.get().getLogger().log(Level.SEVERE,
                        "SelectionPanelField não pode criar Criterion", fex);
            }

        criterion.setFilterType(filterType);
        criterion.setEmptyForAll(emptyForAll);
        criterion.setValue(entity);

        return criterion;
    }

    /**
     * Adiciona um nome predecessor ao nome do field.
     * É utilizado para filtrar atravéz de campos encapsulados por entidades
     * de entidades.
     * Por Exemplo:
     * A Classe Estado tem uma referencia a Pais, então para filtrar os
     * estados pelo nome do pais usariamos "pais.nome" onde "nome" seria o
     * campo e "pais" seria o pré-name.
     *
     * @param prefix
     */
    @Override
    public void addPrefix(String prefix) {
        if (fieldPreName == null || fieldPreName.isEmpty())
            setFieldPreName(prefix);
        else
            setFieldPreName(prefix + "." + fieldPreName);
    }

    @Override
    public void addFieldLayout(FieldLayout fieldLayout) {

        super.addFieldLayout(fieldLayout);

        FieldLayout lc = null;
        
        if (fieldLayout instanceof Criterionable)
            lc = fieldLayout;
        else if (fieldLayout instanceof FieldLayoutGroup){
            List<Criterionable> c =
                    extractCriterionables((FieldLayoutGroup) fieldLayout);
            for (Criterionable criterionable : c) {
                if(criterionable instanceof FieldLayout)
                    lc = fieldLayout;
            }
        }

        if (lc != null){
            lastCriterionable = lc;
        }
    }

    @Override
    public void actionPerformed(Event evt) {

        super.actionPerformed(evt);

        // se não for nulo
        // for um Focus Traversal
        // criado por um dos filhos do lastCriterionable
        if (evt != null){
            if(evt.getName().equals(Event.FOCUS_TRAVERSAL_EVENT) &&
                    evt.getOwnersTree().contains(lastCriterionable)){
                SwingUtilities.invokeLater(this);
            }
        }
    }

    @Override
    public void run() {
        clearMessage();
        try{
            Serializable selectedEntity = selectionLoader.selectEntity();
            setEntity(selectedEntity);
        }catch(NotFoundException ex){
            putMessage(ex.getMessage());
            requestFocus();
        }catch(NotModifiedException ex){
            setEntity(getEntity());
        }
    }

    private void putMessage(String message){
        SelectionMessenger sm = SelectionMessenger.getInstance(this);
        sm.putMessage(message);
    }

    private void clearMessage(){
        SelectionMessenger sm = SelectionMessenger.getInstance(this);
        sm.clearMessage();
    }
}
